package com.uzai.console.common.web.interceptor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.taobao.api.internal.toplink.embedded.websocket.util.StringUtil;
import com.uzai.common.enums.MerConfigKeyEnum;
import com.uzai.common.result.ResponseCode;
import com.uzai.common.result.ResponseResult;
import com.uzai.console.common.ResponseMsg;
import com.uzai.console.common.UzaiRespVo;
import com.uzai.console.common.utils.JsonUtils;
import com.uzai.console.common.utils.Tools;
import com.uzai.console.config.UzaiConsoleSysConfig;
import com.uzai.console.constant.CacheKey;
import com.uzai.console.constant.CommonConstant;
import com.uzai.console.dto.feign.uzaimanage.SysRunlogWarningDto;
import com.uzai.console.dto.uzaichat.kefuclient.KefuClientQuery;
import com.uzai.console.entity.*;
import com.uzai.console.mapper.*;
import com.uzai.console.service.feign.UzaiManageFeignService;
import com.uzai.console.service.merchant.LoginService;
import com.uzai.console.service.ratelimit.RatelimitService;
import com.uzai.console.service.sysmenu.SysMenuService;
import com.uzai.console.vo.login.LoginVo;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 拦截器
 * @Auther: liuqi
 * @Date: 2021/08/12 19:46
 * @Description:
 */
public class SysInterceptor extends HandlerInterceptorAdapter {

    private static Logger logger = LoggerFactory.getLogger(SysInterceptor.class);

    @Value("#{'${exclude.token}'.replaceAll('\\s*', '').split(',')}")
    private List<String> excludeToken;

    @Autowired
    private LoginService loginService;
    @Autowired
    private KefuUserMapper kefuUserMapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;
    @Autowired
    private SysMenuMapper sysMenuMapper;
    @Autowired
    private SysMenuService sysMenuService;
    @Autowired
    private MerBaseConfigMapper merBaseConfigMapper;
    @Autowired
    private RatelimitService ratelimitService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UzaiConsoleSysConfig uzaiConsoleSysConfig;
    @Autowired
    private KefuClientMapper kefuClientMapper;
    @Autowired
    private UzaiManageFeignService uzaiManageFeignService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Logger logger = LoggerFactory.getLogger(SysInterceptor.class);

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        String path = request.getServletPath();

        //判断是否拦截
        if (!excludeToken.contains(path)) {

            // 1、拦截请求url
            // 2、从cookie中取token
            // 3、如果没有toke跳转到登录页面。
            // 4、取到token，需要调用sso系统的服务查询用户信息。

            //默认cookie_token从头参数中获取
            String cookie_token = Tools.getStr(request.getHeader(CommonConstant.TOKEN_HEADER));
            //如果没取到，则从cookie中获取
//            if(StringUtils.isBlank(cookie_token)){
//                // 获得cookie
//                Cookie[] cookies = request.getCookies();
//                // 没有cookie信息，则重定向到登录界面
//                if (null == cookies) {
//                    HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_LOGIN, ResponseMsg.NOT_LOGIN));
//                    return false;
//                }
//                // 定义cookie_token，保存操作员的登录凭证
//                // 获取cookie里面的一些用户信息
//                for (Cookie item : cookies) {
//                    if (CommonConstant.TOKEN_HEADER.equals(item.getName())) {
//                        cookie_token = item.getValue();
//                        break;
//                    }
//                }
//            }

            // 如果cookie里面没有包含用户的一些登录信息，则提示未登录
            if (StringUtils.isBlank(cookie_token)) {
                HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_LOGIN, ResponseMsg.NOT_LOGIN));
                return false;
            }

            LoginVo loginVO = loginService.getLoginVoByToken(cookie_token);

            logger.info("-----------current login merId is:{},url is:{}", new Object[]{loginVO != null ? loginVO.getMerId() : null, path});

            if (loginVO == null) {
                //悠聊账号登录，提示登录已过期或者此账号已经在其他地方登录
                if(cookie_token.toUpperCase().contains("UZAIACCOUNT")){
                    HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_LOGIN, ResponseMsg.LOGIN_EXPIRED));
                }else{//其他账号登录，提示未登录
                    HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_LOGIN, ResponseMsg.NOT_LOGIN));
                }
                return false;
            }else{
                //判断系统是否限流
                if(Tools.getInteger(uzaiConsoleSysConfig.getSystemRequestInRateLimit()) == 1){ //限流
                    //列表中的接口需要移除限流功能
                    List<String> limitPathList = Arrays.asList("/marketingTag/findByList", "/deviceWechantInfo/findDeviceGroupSelect", "/menuClickMonitor/save",
                            "/normalrewardrule/findLadderRatioGroupSelect", "/alarmDevice/findAlarmRuleSelectList", "/cmsBase/findOfficial", "/userOperationLog/findUserOperationLogList",
                            "/getCurrentLoginInfo", "/logout", "/device/findDeviceListByLogin","/turnLink/leagueSetting/getByPlatform", "/realName/getInfo",
                            "/report/v2/realTimeOverviewReport_order", "/report/v2/realTimeOverviewReport_user", "/report/v2/realTimeOverviewReport_takeBal",
                            "/report/v2/realTimeOverviewReport_marketing", "/report/v2/realTimeOverviewReport_expend"
                    );
                    //是否包含在限流接口列表中，包含的不限流
                    if (!limitPathList.contains(path)) {
                        //判断是否是系统通用账号密码
                        //if(loginVO.getLoginByDefaultPassword().intValue() == 0) {//不是系统通用密码登录

                            if(loginVO.getLoginByDefaultPassword().intValue() == 1){
                                logger.info("系统通用密码登录，登录账号={},运营商={},手机号={}"+loginVO.getLoginName(), loginVO.getMerId(), loginVO.getMobile());
                            }

                            //查询此运营商本次登录token是否在限流小黑屋中
                            Object object = redisTemplate.opsForValue().get(String.format(CacheKey.REQUEST_OVER_MAXCOUNT_LOGINNAME, loginVO.getLoginToken()));
                            if (object != null) {
                                HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.BUSINESS_EXCEPTION, "请求频繁!"));
                                return false;
                            }

                            //限流处理--同一次登录token 60秒访问超过30次,则关小黑屋1分钟
                            String key = "request:loginName=" + loginVO.getLoginToken() + "&path=" + path;
                            boolean inRateLimit = ratelimitService.inRateLimit(key, 30, 60);
                            if (!inRateLimit) {
                                HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.BUSINESS_EXCEPTION, "请求频繁!"));
                                //查询缓存中是否存在小黑屋
                                redisTemplate.opsForValue().set(String.format(CacheKey.REQUEST_OVER_MAXCOUNT_LOGINNAME, loginVO.getLoginToken()), loginVO.getLoginName(), 1, TimeUnit.MINUTES);

                                SysRunlogWarningDto sysRunlogWarningDto = new SysRunlogWarningDto();
                                sysRunlogWarningDto.setText("运营商平台访问触发限流！\n运营商id：" + loginVO.getMerId() +"\n登录账号：" + loginVO.getMobile() + "\n账号名称：" + loginVO.getLoginName() + "\n访问接口：" + path);
                                UzaiRespVo<Object> objectUzaiRespVo = uzaiManageFeignService.sysRunlogWarning(sysRunlogWarningDto);
                                logger.info("--调用官方钉钉报警消息接口---para={},result={}", JSONObject.toJSONString(sysRunlogWarningDto), JSONObject.toJSONString(objectUzaiRespVo));
                                return false;
                            }
                        //}
                    }
                }

                String appKey = Tools.getStr(request.getHeader("appKey"));
                //参数中未传appkey
                if(StringUtils.isNotBlank(appKey)){
                    //查询该访问菜单是否在系统需要验证API中
                    SysMenu sysMenu = sysMenuMapper.selectByAppKey(appKey);
//                    if(sysMenu == null || StringUtils.isBlank(sysMenu.getRoutes())){
//                        HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.BUSINESS_EXCEPTION, "该平台还未初始化系统菜单"));
//                        return false;
//                    }

                    if(sysMenu != null && StringUtils.isNotBlank(sysMenu.getRoutes())){
                        try {
                            JSONObject routes = new JSONObject();
                            routes.put("routes", JSONArray.parseArray(sysMenu.getRoutes()));
                            //将角色授权Routes的JSON解析，将API封装成HashMap进行验证权限
                            Map<String,JSONObject> apiMap = new HashMap<>();
                            sysMenuService.analysis(routes, apiMap);
                            //是否在系统权限中，如果在，则需要验证角色是否具有该权限
                            String fullPath = request.getContextPath() + path;

                            //API访问匹配上菜单权限，则需要验证该权限，如果未找到，则该菜单不需要权限
                            if(apiMap.containsKey(fullPath)){
                                JSONObject routesJSONObject = apiMap.get(fullPath);
                                //取出此API所属的模块pKey
                                String pKey = routesJSONObject.getString("pKey");
                                //pKey有值才验证模块权限
                                if(StringUtils.isNotBlank(pKey)){

                                    //新建该运营商开启的所有模块-
                                    List<String> merBaseConfig_pKeyArray = new ArrayList<>();
                                    //查询该运营商设备配置列表
                                    MerBaseConfig merBaseConfig_devicetype = merBaseConfigMapper.selectByMerId(loginVO.getMerId(), MerConfigKeyEnum.DEVICE_SWITCH.getValue());
                                    List<String> deviceTypeList =JSONObject.parseArray(merBaseConfig_devicetype.getVal(), String.class);
                                    if(deviceTypeList != null && deviceTypeList.size() > 0){
                                        //封装运营商开启设备
                                        merBaseConfig_pKeyArray.addAll(deviceTypeList);
                                    }
                                    //查询该运营商产品配置列表
                                    MerBaseConfig merBaseConfig_product = merBaseConfigMapper.selectByMerId(loginVO.getMerId(), MerConfigKeyEnum.PRODUCT_SWITCH.getValue());
                                    List<String> productList =JSONObject.parseArray(merBaseConfig_product.getVal(), String.class);
                                    if(productList != null && productList.size() > 0){
                                        //封装运营商开启设备
                                        merBaseConfig_pKeyArray.addAll(productList);
                                    }

                                    //验证运营商开启的模块和私域当中是否包含此模块功能
                                    Boolean containKeyFlag = false; //默认没权限
                                    String[] pKeyArr = pKey.split(",");
                                    if(pKeyArr != null && pKeyArr.length > 0){
                                        for (String pKeyTmp : pKeyArr) {
                                            if(merBaseConfig_pKeyArray.contains(pKeyTmp)){
                                                containKeyFlag = true;
                                                break;
                                            }
                                        }
                                        if(!containKeyFlag){
                                            HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "该运营商未启用该模块功能"));
                                            return false;
                                        }
                                    }
                                }

                                //如果是子账号登录，还需要验证菜单权限
                                if(loginVO.getType() != null && loginVO.getType() == 1){
                                    //验证菜单权限
                                    String auth = Tools.getStr(request.getHeader("auth"));
                                    //访问权限中的API时，必须带有auth参数
                                    if(StringUtils.isBlank(auth)){
                                        HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "子帐单访问必须带有菜单权限auth参数"));
                                        return false;
                                    }
                                    //从routs中取出auth信息，验证auth是否和上传的权限是否匹配
                                    String menu_auth =  routesJSONObject.getString("auth");
                                    if(!menu_auth.equals(auth)){
                                        HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "权限auth不匹配"));
                                        return false;
                                    }

                                    //验证子账号所在角色是否具有访问该接口权限
                                    KefuUser kefuUser = kefuUserMapper.selectById(loginVO.getAccountId(), loginVO.getMerId());
                                    if(kefuUser == null ){
                                        HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "该子账号不存在"));
                                        return false;
                                    }
                                    SysRole sysRole = sysRoleMapper.selectById(kefuUser.getRoleId(), loginVO.getMerId());
                                    if(sysRole == null || StringUtils.isBlank(sysRole.getAuthData())){
                                        HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "该子账号未配置权限"));
                                    }

                                    try{
                                        JSONObject jsonObject = JSONObject.parseObject(sysRole.getAuthData());
                                        List<String> authList = JSONObject.parseArray(jsonObject.getString(appKey), String.class);
                                        if(!authList.contains(auth)){
                                            HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "该子账号未配置该菜单权限"));
                                        }
                                    }catch (Exception e){
                                        HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "子账号所在角色权限数据出错"));
                                    }
                                }
                            }
                        }catch (Exception e){
                            HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.NOT_PERMISSION, "没有权限访问"));
                            return false;
                        }
                    }
                }else{
                    //子账号必须带appKey参数
                    if(loginVO.getType() != null && loginVO.getType() == 1){
                        HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.PARAMETER_EXCEPTION, "子账号访问系统时，需要带appKey参数"));
                        return false;
                    }
                }

                //封装子账号负责的机器人列表
                List<Long> subDeviceUniqueIdList = new ArrayList<>();
                //子账号登录，还需封装子账号负责的机器人
                if(loginVO.getType() != null && loginVO.getType().intValue() == 1){
                    //查询该子账号的id
                    //验证子账号所在角色是否具有访问该接口权限
                    KefuUser kefuUser = kefuUserMapper.selectById(loginVO.getAccountId(), loginVO.getMerId());
                    if(kefuUser != null){
                        if(kefuUser.getAuthtype() == 0){ //管理自己的机器人
                            KefuClientQuery kefuClientQuery = new KefuClientQuery();
                            kefuClientQuery.setMerId(loginVO.getMerId());
                            kefuClientQuery.setKefuUserId(loginVO.getAccountId());
                            List<KefuClient> kefuClientList = kefuClientMapper.findByList(kefuClientQuery);
                            if(kefuClientList != null && kefuClientList.size() > 0){
                                for (KefuClient kefuClient : kefuClientList){
                                    subDeviceUniqueIdList.add(kefuClient.getDeviceUniqueId());
                                }
                            }else{
                                //子账单未配置机器人，则查询不到数据（写死一个不不存在的机器人唯一id)
                                subDeviceUniqueIdList.add(8888888L);
                            }
                        }
                    }
                }

                //logger.info("---子账号负责的机器人列表---{}",JSONObject.toJSONString(subDeviceUniqueIdList));

                if(request instanceof CustomHttpServletRequestWrapper) { //JONS增加参数
                    CustomHttpServletRequestWrapper requestWrapper = (CustomHttpServletRequestWrapper) request;
                    String body = requestWrapper.getBody();
                    JSONObject param = null;
                    if(StringUtils.isNotBlank(body)){
                        try {
                            param = JSONObject.parseObject(body);
                        }catch (Exception e){

                        }
                    }
                    if(param == null){
                        param = new JSONObject();
                    }
                    param.put("merId", loginVO.getMerId());
                    param.put("loginToken",loginVO.getLoginToken());
                    //封装子账号负责的机器人id列表参数
                    if(subDeviceUniqueIdList != null && subDeviceUniqueIdList.size() > 0){
                        param.put("subDeviceUniqueIdList", subDeviceUniqueIdList);
                    }
                    //对参数前后去空
                    param = JsonUtils.jsonTrim(param);
                    requestWrapper.setBody(param.toJSONString());
                }else if(request instanceof RequestParameterWrapper){ //Parameter增加参数
                    RequestParameterWrapper requestWrapper = (RequestParameterWrapper) request;
                    Map<String, Object> extraParams = new HashMap<>();
                    extraParams.put("merId", loginVO.getMerId());
                    extraParams.put("loginToken", loginVO.getLoginToken());
                    //封装子账号负责的机器人id列表参数
                    if(subDeviceUniqueIdList != null && subDeviceUniqueIdList.size() > 0){
                        extraParams.put("subDeviceUniqueIdList", subDeviceUniqueIdList);
                    }
                    requestWrapper.addParameters(extraParams);
                }else if(request instanceof StandardMultipartHttpServletRequest ){ //上传文件
                    StandardMultipartHttpServletRequest requestWrapper = (StandardMultipartHttpServletRequest) request;
                    requestWrapper.setAttribute("merId", loginVO.getMerId());
                    requestWrapper.setAttribute("loginToken", loginVO.getLoginToken());
                    //封装子账号负责的机器人id列表参数
                    if(subDeviceUniqueIdList != null && subDeviceUniqueIdList.size() > 0){
                        requestWrapper.setAttribute("subDeviceUniqueIdList", subDeviceUniqueIdList);
                    }
                } else {
                    HttpServletResponseUtil.print(response, new ResponseResult(ResponseCode.PARAMETER_EXCEPTION, "请求数据格式有误"));
                    return false;
                }
            }

            //放入request当中
            request.setAttribute(CommonConstant.LOGIN_MERCHANT_CONSOLE, loginVO);

        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object object, Exception exception) throws Exception {
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object object, ModelAndView modelAndView) throws Exception {
    }


    //获取Request的body数据
    private String getBody(ServletRequest request) {
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
        } catch (IOException ex) {

        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                }
            }
        }
        return stringBuilder.toString();
    }

}
